package asruby.runtime
{
import asruby.Ruby;
import asruby.lang.RubyArray;
import asruby.lang.RubyClass;
import asruby.lang.RubyModule;
import asruby.parser.LocalStaticScope;
import asruby.parser.StaticScope;
import asruby.runtime.scope.ManyVarsDynamicScope;
	
public class ThreadContext
{
  	private  static var INITIAL_SIZE: int = 50;
    
    private  var runtime: Ruby;
    
    // Is this thread currently with in a function trace?
    private var isWithinTrace: Boolean;
    
    // Is this thread currently doing an defined? defined should set things like $!
    private var _isWithinDefined: Boolean;        
    
    // Error info is per-thread
    private var errorInfo: IRubyObject;
    
    //private UnsynchronizedStack parentStack;
    private var parentStack: Array = new Array();
    private var parentIndex:int = -1;
    
    //private UnsynchronizedStack frameStack;
    private var frameStack: Array = new Array;
    private var frameIndex:int = -1;
    
    // Array of active dynamic scopes.  Each of these may have captured other dynamic scopes
    // to implement closures.
    private var scopeStack: Array = new Array;
    private var scopeIndex:int = -1;
    
    private var catchStack: Array = new Array;
    private var catchIndex: int = -1;
    
    // File where current executing unit is being evaluated
    private var file:String = "";
    
    // Line where current executing unit is being evaluated
    private var line: int = 0;
    
    /**
     * Constructor for Context.
     */
    private function ThreadContext0(runtime: Ruby) {
        this.runtime = runtime;
        
        // init errorInfo to nil
        errorInfo = runtime.getNil();
        
        // TOPLEVEL self and a few others want a top-level scope.  We create this one right
        // away and then pass it into top-level parse so it ends up being the top level.
        var topStaticScope: StaticScope = new LocalStaticScope(null);
        pushScope(new ManyVarsDynamicScope(topStaticScope, null));
            
        for (var i: int = 0; i < frameStack.length; i++) {
            frameStack[i] = new Frame();
        }
    }
    
    var lastCallType:CallType;
    
    var lastVisibility:Visibility;
    
    var lastExitStatus:IRubyObject;
    
    public function  getRuntime(): Ruby {
        return runtime;
    }
    
    public function  getErrorInfo(): IRubyObject {
        return errorInfo;
    }
    
    public function  setErrorInfo(errorInfo: IRubyObject): IRubyObject {
        this.errorInfo = errorInfo;
        return errorInfo;
    }
    
    /**
     * Returns the lastCallStatus.
     * @return LastCallStatus
     */
    public function setLastCallStatus(callType: CallType): void {
       lastCallType = callType;
    }

    public function  getLastCallType(): CallType {
        return lastCallType;
    }

    public function  setLastVisibility(visibility: Visibility): void {
        lastVisibility = visibility;
    }

    public function  getLastVisibility(): Visibility {
        return lastVisibility;
    }
    
    public function  getLastExitStatus(): IRubyObject {
        return lastExitStatus;
    }
    
    public function  setLastExitStatus(lastExitStatus: IRubyObject): void {
        this.lastExitStatus = lastExitStatus;
    }

    public function  printScope(): void {
        trace("SCOPE STACK:");
        for (var i:int = 0; i <= scopeIndex; i++) {
            trace(scopeStack[i]);
        }
    }

    public function  getCurrentScope(): DynamicScope {
        return scopeStack[scopeIndex];
    }
    
    public function  getPreviousScope(): DynamicScope {
        return scopeStack[scopeIndex - 1];
    }

   	public function  preScopedBody(scope: DynamicScope): void {
        pushScope(scope);
    }
    
    public function  postScopedBody(): void {
        popScope();
    }    

	private function  expandFramesIfNecessary(newMax: int): void {
        if (newMax == frameStack.length) {
            var newSize: int = frameStack.length * 2;
            var newFrameStack: Array = frameStack.length(0, frameStack.length);
            
            for (var i: int = frameStack.length; i < newSize; i++) {
                newFrameStack[i] = new Frame();
            }
            
            frameStack = newFrameStack;
        }
    }
    
    private function  expandParentsIfNecessary(): void {
        if (parentIndex + 1 == parentStack.length) {
            var newSize: int = parentStack.length * 2;
            var newParentStack: Array = parentStack.slice(0, parentStack.length);
 			parentStack = newParentStack;
        }
    }
    
    public function  pushScope(scope: DynamicScope): void {
        scopeStack[++scopeIndex] = scope;
        expandScopesIfNecessary();
    }
    
    public function  popScope(): void {
        scopeStack[scopeIndex--] = null;
    }
    
	private function  expandScopesIfNecessary(): void {
        if (scopeIndex + 1 == scopeStack.length) {
            var newSize: int = scopeStack.length * 2;
            var newScopeStack: Array = scopeStack.slice(0, scopeStack.length);
            scopeStack = newScopeStack;
        }
    }
        
//    public IRubyObject getLastline() {
//        var value: IRubyObject = getCurrentScope().getLastLine();
//        
//        // DynamicScope does not preinitialize these values since they are virtually never used.
//        return value == null ? runtime.getNil() : value;
//    }
//    
//    public void setLastline(IRubyObject value) {
//        getCurrentScope().setLastLine(value);
//    }
    
    //////////////////// CATCH MANAGEMENT ////////////////////////
/*     private function  expandCatchIfNecessary(): void {
        if (catchIndex + 1 == catchStack.length) {
            var newSize: int = catchStack.length * 2;
            var newCatchStack: CatchTarget[] = new CatchTarget[newSize];
            
            System.arraycopy(catchStack, 0, newCatchStack, 0, catchStack.length);
            var catchStack:  = newCatchStack;
        }
    }
     
    public function  pushCatch(catchTarget: CatchTarget): void {
        catchStack[++catchIndex] = catchTarget;
        // expandCatchIfNecessary();
    }
    */
    public function  popCatch(): void {
        catchIndex--;
    }
    
    public function  getActiveCatches(): Array {
        if (catchIndex < 0) return new Array();
        
        var activeCatches: Array = catchStack.slice(0, catchIndex + 1);
        return activeCatches;
    }
    
    //////////////////// FRAME MANAGEMENT ////////////////////////
    private function  pushFrameCopy(): void {
        var currentFrame: Frame = getCurrentFrame();
        frameStack[++frameIndex].updateFrame(currentFrame);
        //expandFramesIfNecessary(frameIndex + 1);
    }
    
    private function  pushFrameCopy2(frame: Frame): Frame {
        frameStack[++frameIndex].updateFrame(frame);
        //expandFramesIfNecessary(frameIndex + 1);
        return frameStack[frameIndex];
    }
    
    private function  pushFrame(frame: Frame): void {
        frameStack[++frameIndex] = frame;
        //expandFramesIfNecessary(frameIndex + 1);
    }
    
    private function  pushCallFrame( clazz:RubyModule,  name:String,
                                self:IRubyObject, block:Block, jumpTarget:JumpTarget) {
        pushFrame4(clazz, name, self, block, jumpTarget);        
    }
    
    private function  pushBacktraceFrame(name: String): void {
        pushFrame3(name);        
    }
    
    private function  pushFrame3(name: String): void {
        frameStack[++frameIndex].updateFrame(name, file, line);
        //expandFramesIfNecessary(frameIndex + 1);
    }

    private function  pushFrame4(clazz:RubyModule,  name:String,
                                self:IRubyObject, block:Block, jumpTarget:JumpTarget) {
        frameStack[++frameIndex].updateFrame(clazz, self, name, block, file, line, jumpTarget);
        //expandFramesIfNecessary(frameIndex + 1);
    }
    
    private function  pushFrame5(): void {
        frameStack[++frameIndex].updateFrame(file, line);
        expandFramesIfNecessary(frameIndex + 1);
    }
    
    private function  popFrame(): void {
        var frame: Frame = frameStack[frameIndex];
        frameIndex--;
        setFile(frame.getFile());
        setLine(frame.getLine());
    }
        
    private function  popFrameReal(): void {
        var frame: Frame = frameStack[frameIndex];
        frameStack[frameIndex] = new Frame();
        frameIndex--;
        setFile(frame.getFile());
        setLine(frame.getLine());
    }
    
    public function  getCurrentFrame(): Frame {
        return frameStack[frameIndex];
    }
    
    public function  getNextFrame(): Frame {
        expandFramesIfNecessary(frameIndex + 1);
        return frameStack[frameIndex + 1];
    }
    
    public function  getPreviousFrame(): Frame {
        var size: int = frameIndex + 1;
        return size <= 1 ? null : frameStack[size - 2];
    }
    
    public function  getFrameCount(): int {
        return frameIndex + 1;
    }
    
    public function  getFrameName(): String {
        return getCurrentFrame().getName();
    }
    
    public function  getFrameSelf(): IRubyObject {
        return getCurrentFrame().getSelf();
    }
    
    public function  getFrameJumpTarget(): JumpTarget {
        return getCurrentFrame().getJumpTarget();
    }
    
    public function  setFrameJumpTarget(target: JumpTarget): void {
        getCurrentFrame().setJumpTarget(target);
    }
    
    public function  getFrameKlazz(): RubyModule {
        return getCurrentFrame().getKlazz();
    }
    
    public function  getFrameBlock(): Block {
        return getCurrentFrame().getBlock();
    }
    
    public function  getFile(): String {
        return file;
    }
    
    public function  getLine(): int {
        return line;
    }
    
    public function  setFile(file: String): void {
        this.file = file;
    }
    
    public function  setLine(line: int): void {
        this.line = line;
    }
    
    public function  getCurrentVisibility(): Visibility {
        return getCurrentFrame().getVisibility();
    }
    
    public function  getPreviousVisibility(): Visibility {
        return getPreviousFrame().getVisibility();
    }
    
    public function  setCurrentVisibility(visibility: Visibility): void {
        getCurrentFrame().setVisibility(visibility);
    }
    
/*     public function  pollThreadEvents(): void {
        getThread().pollThreadEvents(this);
    } */
    
    public function  pushRubyClass(currentModule: RubyModule): void {
        // FIXME: this seems like a good assertion, but it breaks compiled code and the code seems
        // to run without it...
        //assert currentModule != null : "Can't push null RubyClass";
        
        parentStack[++parentIndex] = currentModule;
        expandParentsIfNecessary();
    }
    
    public function  popRubyClass(): RubyModule {
        var ret: RubyModule = parentStack[parentIndex];
        parentStack[parentIndex--] = null;
        return ret;
    }
    
    public function  getRubyClass(): RubyModule {
        //assert !(parentIndex == -1) : "Trying to get RubyClass from empty stack";
        
        var parentModule: RubyModule = parentStack[parentIndex];
        
        return parentModule.getNonIncludedClass();
    }
    
    public function  getBindingRubyClass(): RubyModule {
        var parentModule: RubyModule = null;
        if(parentIndex == 0) {
           	parentModule = parentStack[parentIndex];
        } else {
            parentModule = parentStack[parentIndex-1];
            
        }
        return parentModule.getNonIncludedClass();
    }
    
    public function  getConstantDefined(internedName: String): Boolean {
        var result:IRubyObject;
        var undef: IRubyObject = runtime.getUndef();
        
        // flipped from while to do to search current class first
        for (var scope:StaticScope = getCurrentScope().getStaticScope(); scope != null; scope = scope.getPreviousCRefScope()) {
            var module: RubyModule = scope.getModule();
             if ((result = module.fastFetchConstant(internedName)) != null) {
                if (result != undef) return true;
                return runtime.getLoadService().autoloadFor(module.getName() + "::" + internedName) != null;
            }
        }
        
        return getCurrentScope().getStaticScope().getModule().fastIsConstantDefined(internedName);
    }
    
    /**
     * Used by the evaluator and the compiler to look up a constant by name
     */
    public function  getConstant(internedName: String): IRubyObject {
        var scope: StaticScope = getCurrentScope().getStaticScope();
        var object: RubyClass = runtime.getObject();
        var undef: IRubyObject = runtime.getUndef();
        var result:IRubyObject;
        
        // flipped from while to do to search current class first
        do {
            var klass: RubyModule = scope.getModule();
            
            // Not sure how this can happen
            //if (NIL_P(klass)) return rb_const_get(CLASS_OF(self), id);
            if ((result = klass.fastFetchConstant(internedName)) != null) {
                if (result != undef) {
                    return result;
                }
                klass.deleteConstant(internedName);
                if (runtime.getLoadService().autoload(klass.getName() + "::" + internedName) == null) break;
                continue;
            }
            scope = scope.getPreviousCRefScope();
        } while (scope != null && scope.getModule() != object);
        
        return getCurrentScope().getStaticScope().getModule().fastGetConstant(internedName);
    }
    
    /**
     * Used by the evaluator and the compiler to set a constant by name
     * This is for a null const decl
     */
    public function  setConstantInCurrent(internedName: String, result: IRubyObject): IRubyObject {
        var module:RubyModule;

        if ((module = getCurrentScope().getStaticScope().getModule() ) != null) {
            module.fastSetConstant(internedName, result);
            return result;
        }

        // TODO: wire into new exception handling mechanism
        throw runtime.newTypeError("no class/module to define constant");
    }
    
    /**
     * Used by the evaluator and the compiler to set a constant by name.
     * This is for a Colon2 const decl
     */
    public function  setConstantInModule(internedName: String, target: IRubyObject, result: IRubyObject): IRubyObject {
        if (!(target is RubyModule)) {
            throw runtime.newTypeError(target.toString() + " is not a class/module");
        }
        var module:RubyModule = RubyModule(target);
        module.fastSetConstant(internedName, result);
        
        return result;
    }
    
    /**
     * Used by the evaluator and the compiler to set a constant by name
     * This is for a Colon2 const decl
     */
    public function  setConstantInObject(internedName: String, result: IRubyObject): IRubyObject {
        runtime.getObject().fastSetConstant(internedName, result);
        
        return result;
    }
    
    private static function  addBackTraceElement(backtrace: RubyArray, frame: Frame, previousFrame: Frame): void {
        if (frame != previousFrame && // happens with native exceptions, should not filter those out
                frame.getName() != null && 
                frame.getName() == previousFrame.getName() &&
                frame.getFile() == previousFrame.getFile() &&
                frame.getLine() == previousFrame.getLine()) {
            return;
        }
        
        var buf: String = new String(60);
        buf.concat(frame.getFile()).concat(':').concat(frame.getLine() + 1);
        
        if (previousFrame.getName() != null) {
            buf.concat(":in `").concat(previousFrame.getName()).concat('\'');
        }
        
        backtrace.append(backtrace.getRuntime().newString(buf.toString()));
    }
    
    /**
     * Create an Array with backtrace information.
     * @param runtime
     * @param level
     * @param nativeException
     * @return an Array with the backtrace
     */
    public static function  createBacktraceFromFrames(runtime: Ruby, backtraceFrames: Array): IRubyObject {
        var backtrace: RubyArray = runtime.newArray();
        
        if (backtraceFrames == null || backtraceFrames.length <= 0) return backtrace;
        
        var traceSize: int = backtraceFrames.length;

        for (var i: int = traceSize - 1; i > 0; i--) {
            var frame: Frame = backtraceFrames[i];
            // We are in eval with binding break out early
            if (frame.isBindingFrame()) break;

            addBackTraceElement(backtrace, frame, backtraceFrames[i - 1]);
        }
        
        return backtrace;
    }
    
    /**
     * Create an Array with backtrace information.
     * @param runtime
     * @param level
     * @param nativeException
     * @return an Array with the backtrace
     */
    public function  createBacktrace(level: int, nativeException: Boolean): Array {
        var traceSize: int = frameIndex - level + 1;
        var traceFrames:Array;
        
        if (traceSize <= 0) return null;
        
        if (nativeException) {
            // assert level == 0;
            traceFrames = new Array();
            traceFrames[traceSize] = frameStack[frameIndex];
        } else {
            traceFrames = new Array();
        }
        
        traceFrames = frameStack.slice(0, traceSize);
        
        return traceFrames;
    }
        
    public function  preForBlock(binding: Binding, klass: RubyModule): void {
        var f: Frame = binding.getFrame();
        f.setFile(file);
        f.setLine(line);
        pushFrame(f);
        getCurrentFrame().setVisibility(binding.getVisibility());
        pushScope(binding.getDynamicScope());
        pushRubyClass((klass != null) ? klass : binding.getKlass());
    }
    
    public function  preYieldSpecificBlock(binding: Binding, scope: StaticScope, klass: RubyModule): void {
        var f: Frame = pushFrameCopy2(binding.getFrame());
        f.setFile(file);
        f.setLine(line);
        f.setVisibility(binding.getVisibility());
        // new scope for this invocation of the block, based on parent scope
        pushScope(DynamicScope.newDynamicScope(scope, binding.getDynamicScope()));
        pushRubyClass((klass != null) ? klass : binding.getKlass());
    }
    
    public function  preYieldLightBlock(binding: Binding, emptyScope: DynamicScope, klass: RubyModule): void {
        var f: Frame = pushFrameCopy2(binding.getFrame());
        f.setFile(file);
        f.setLine(line);
        f.setVisibility(binding.getVisibility());
        // just push the same empty scope, since we won't use one
        pushScope(emptyScope);
        pushRubyClass((klass != null) ? klass : binding.getKlass());
    }
    
    public function  preYieldNoScope(binding: Binding, klass: RubyModule): void {
        var f: Frame = pushFrameCopy2(binding.getFrame());
        f.setFile(file);
        f.setLine(line);
        f.setVisibility(binding.getVisibility());
        pushRubyClass((klass != null) ? klass : binding.getKlass());
    }
    
    public function  preEvalWithBinding(binding: Binding): void {
        var frame: Frame = binding.getFrame();
        frame.setIsBindingFrame(true);
        pushFrame(frame);
        getCurrentFrame().setVisibility(binding.getVisibility());
        pushRubyClass(binding.getKlass());
    }
    
    public function  postEvalWithBinding(binding: Binding): void {
        binding.getFrame().setIsBindingFrame(false);
        popFrameReal();
        popRubyClass();
    }
    
    public function  postYield(binding: Binding): void {
        popScope();
        popFrameReal();
        popRubyClass();
    }
    
    public function  postYieldLight(binding: Binding): void {
        popScope();
        popFrameReal();
        popRubyClass();
    }
    
    public function  postYieldNoScope(): void {
        popFrameReal();
        popRubyClass();
    }
    
    /**
     * Is this thread actively tracing at this moment.
     *
     * @return true if so
     * @see org.jruby.Ruby#callTraceFunction(String, ISourcePosition, IRubyObject, String, IRubyObject)
     */
    public function  isWithinTrace2(): Boolean {
        return isWithinTrace;
    }
    
    /**
     * Set whether we are actively tracing or not on this thread.
     *
     * @param isWithinTrace true is so
     * @see org.jruby.Ruby#callTraceFunction(String, ISourcePosition, IRubyObject, String, IRubyObject)
     */
    public function  setWithinTrace(isWithinTrace: Boolean): void {
        this.isWithinTrace = isWithinTrace;
    }
    
    /**
     * Is this thread actively in defined? at the moment.
     *
     * @return true if within defined?
     */
    public function isWithinDefined(): Boolean {
        return _isWithinDefined;
    }
    
    /**
     * Set whether we are actively within defined? or not.
     *
     * @param isWithinDefined true if so
     */
    public function setWithinDefined(isWithinDefined: Boolean): void {
        this._isWithinDefined = isWithinDefined;
    }
}
}